//
// Created by Administrator on 2020/6/20 0020.
//
#include <RSAUtils.h>
#include <unistd.h>
#include <android_utils.h>
#include <detect.h>
#include <common.h>

jobject toPublicKey(JNIEnv *jniEnv, jstring keyStr) {

    if (jstringIsEmpty(jniEnv, keyStr)) {
        throwIllegalArgumentException(jniEnv, "keyStr is null or \"\"");
        return NULL;
    }

    static jmethodID KeyFactory_getInstance,
            X509EncodedKeySpec_init,
            KeyFactory_generatePublic;

    jclass KeyFactory_clazz = (*jniEnv)->FindClass(jniEnv, "java/security/KeyFactory");
    checkExc(jniEnv);

    jclass X509EncodedKeySpec_clazz = (*jniEnv)->FindClass(jniEnv,
                                                           "java/security/spec/X509EncodedKeySpec");
    checkExc(jniEnv);


    if (KeyFactory_getInstance == NULL) {
        KeyFactory_getInstance = (*jniEnv)->GetStaticMethodID(jniEnv, KeyFactory_clazz,
                                                              "getInstance",
                                                              "(Ljava/lang/String;)Ljava/security/KeyFactory;");
        checkExc(jniEnv);
    }

    if (X509EncodedKeySpec_init == NULL) {
        X509EncodedKeySpec_init = (*jniEnv)->GetMethodID(jniEnv, X509EncodedKeySpec_clazz, "<init>",
                                                         "([B)V");
        checkExc(jniEnv);
    }

    if (KeyFactory_generatePublic == NULL) {
        KeyFactory_generatePublic = (*jniEnv)->GetMethodID(jniEnv, KeyFactory_clazz,
                                                           "generatePublic",
                                                           "(Ljava/security/spec/KeySpec;)Ljava/security/PublicKey;");
        checkExc(jniEnv);
    }

    //RSA
    jstring rsa = char2jstring(jniEnv, "RSA");
    //KeyFactory keyFactory = KeyFactory.getInstance("RSA");
    jobject keyFactory = (*jniEnv)->CallStaticObjectMethod(jniEnv, KeyFactory_clazz,
                                                           KeyFactory_getInstance, rsa);
    //Base64Utils.decode(keyStr)
    jbyteArray array = decode(jniEnv, keyStr);
    //X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64Utils.decode(keyStr));
    jobject keySpec = (*jniEnv)->NewObject(jniEnv, X509EncodedKeySpec_clazz,
                                           X509EncodedKeySpec_init, array);
    checkExc(jniEnv);
    //keyFactory.generatePublic(keySpec)
    jobject ret = (*jniEnv)->CallObjectMethod(jniEnv, keyFactory, KeyFactory_generatePublic,
                                              keySpec);


    (*jniEnv)->DeleteLocalRef(jniEnv, X509EncodedKeySpec_clazz);
    (*jniEnv)->DeleteLocalRef(jniEnv, KeyFactory_clazz);
    (*jniEnv)->DeleteLocalRef(jniEnv, keySpec);
    (*jniEnv)->DeleteLocalRef(jniEnv, rsa);
    return ret;
//    return NULL;
}

jstring rsaEncrypt(JNIEnv *jniEnv, jobject thiz, jstring data, jstring keyStr) {

    if (jstringIsEmpty(jniEnv, keyStr)) {
        throwIllegalArgumentException(jniEnv, "keyStr is null or \"\"");
        return NULL;
    }

    jstring ret = NULL;
    if ((*jniEnv)->MonitorEnter(jniEnv, thiz) == JNI_OK) {

        jclass Cipher_clazz = NULL, URLEncoder_clazz = NULL, InvalidKeyException_clazz;
        static jmethodID
                Cipher_getInstance = NULL,
                Cipher_init = NULL,
                Cipher_doFinal = NULL,
                URLEncoder_encode = NULL;

        Cipher_clazz = (*jniEnv)->FindClass(jniEnv, "javax/crypto/Cipher");
        checkExc(jniEnv);
        URLEncoder_clazz = (*jniEnv)->FindClass(jniEnv, "java/net/URLEncoder");
        checkExc(jniEnv);
        InvalidKeyException_clazz = (*jniEnv)->FindClass(jniEnv,
                                                         "java/security/InvalidKeyException");
        checkExc(jniEnv);


        if (URLEncoder_encode == NULL) {
            URLEncoder_encode = (*jniEnv)->GetStaticMethodID(jniEnv, URLEncoder_clazz, "encode",
                                                             "(Ljava/lang/String;)Ljava/lang/String;");
            checkExc(jniEnv);
        }

        if (Cipher_getInstance == NULL) {
            Cipher_getInstance = (*jniEnv)->GetStaticMethodID(jniEnv, Cipher_clazz,
                                                              "getInstance",
                                                              "(Ljava/lang/String;)Ljavax/crypto/Cipher;");
            checkExc(jniEnv);
        }

        if (Cipher_init == NULL) {
            Cipher_init = (*jniEnv)->GetMethodID(jniEnv, Cipher_clazz,
                                                 "init",
                                                 "(ILjava/security/Key;)V");
            checkExc(jniEnv);
        }

        if (Cipher_doFinal == NULL) {
            Cipher_doFinal = (*jniEnv)->GetMethodID(jniEnv, Cipher_clazz, "doFinal", "([B)[B");
            checkExc(jniEnv);
        }

        // key = toPublicKey(keySrc);
        jobject key = toPublicKey(jniEnv, keyStr);
        if (key == NULL) {

            LOGI("key is NULL");
            goto end;
        }

        // cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
        const char *model = "RSA/ECB/PKCS1Padding";
        jstring transformation = char2jstring(jniEnv, model);
        jobject cipher = (*jniEnv)->CallStaticObjectMethod(jniEnv, Cipher_clazz, Cipher_getInstance,
                                                           transformation);

        //cipher.init(Cipher.ENCRYPT_MODE, key);
        (*jniEnv)->CallVoidMethod(jniEnv, cipher, Cipher_init, 1, key);
        checkExc(jniEnv);
        // bytes = cipher.doFinal(data.getBytes());
        jbyteArray array = jstring2jbyteArray(jniEnv, data);
        LOGI("array 1 length %d", (*jniEnv)->GetArrayLength(jniEnv, array));
        jbyteArray bytes = (*jniEnv)->CallObjectMethod(jniEnv, cipher, Cipher_doFinal, array);
        checkExc(jniEnv);
        LOGI("bytes 2 size %d", (*jniEnv)->GetArrayLength(jniEnv, bytes));
        //Base64Utils.encode(bytes)
        ret = encode(jniEnv, bytes);
//        //URLEncoder.encode(Base64Utils.encode(bytes));
//        ret = (*jniEnv)->CallStaticObjectMethod(jniEnv, URLEncoder_clazz, URLEncoder_encode,
//                                                bytes_bs64_str);


        end:
        (*jniEnv)->DeleteLocalRef(jniEnv, URLEncoder_clazz);
        (*jniEnv)->DeleteLocalRef(jniEnv, Cipher_clazz);
        (*jniEnv)->DeleteLocalRef(jniEnv, InvalidKeyException_clazz);
//    (*jniEnv)->ReleaseStringUTFChars(jniEnv, transformation, model);
        (*jniEnv)->MonitorExit(jniEnv, thiz);
    }
    return ret;
//    return NULL;
}
